#include "textedit.h"
#include "linenumber.h"

TextEdit::TextEdit(QFont font, QWidget *parent) : QPlainTextEdit(parent)
{
    lineNumberArea = new LineNumber(this);

    connect(this, SIGNAL(blockCountChanged(int)), this, SLOT(updateLineNumberAreaWidth(int)));
    connect(this, SIGNAL(updateRequest(QRect,int)), this, SLOT(updateLineNumberArea(QRect,int)));
    connect(this, SIGNAL(cursorPositionChanged()), this, SLOT(highlightCurrentLine()));

    setStyleSheet("TextEdit { selection-color: white; selection-background-color: darkblue; border-right: 1px solid #ddd; background-attachment: fixed; background: white url(:/bgimg/bgimg/"+QString::number((qrand()%7)+1)+".png) no-repeat right bottom; }");

    highlightCurrentLine();

    setFont(font);
    setLineWrapMode(QPlainTextEdit::NoWrap);

    setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
    setAcceptDrops(false);

    setTabStopWidth(40);
}

 TextEdit::~TextEdit()
 {
     delete c;
     delete lineNumberArea;
 }

 void TextEdit::setCompleter(QCompleter *completer)
 {
     c = completer;

     if (!c)
         return;

     c->setWidget(this);
     c->setCompletionMode(QCompleter::PopupCompletion);
     c->setCaseSensitivity(Qt::CaseInsensitive);
     QObject::connect(c, SIGNAL(activated(QString)),
                      this, SLOT(insertCompletion(QString)));
 }

 QCompleter *TextEdit::completer() const
 {
     return c;
 }

 void TextEdit::insertCompletion(const QString& completion)
 {
     if (c->widget() != this)
         return;
     QTextCursor tc = textCursor();
     int extra = completion.length() - c->completionPrefix().length();
     tc.movePosition(QTextCursor::Left);
     tc.movePosition(QTextCursor::EndOfWord);
     tc.insertText(completion.right(extra));
     setTextCursor(tc);
 }

 QString TextEdit::textUnderCursor() const
 {
     QTextCursor tc = textCursor();
     tc.select(QTextCursor::WordUnderCursor);
     return tc.selectedText();
 }

 void TextEdit::focusInEvent(QFocusEvent *e)
 {
     if (c)
         c->setWidget(this);
     QPlainTextEdit::focusInEvent(e);
 }

 void TextEdit::keyPressEvent(QKeyEvent *e)
 {
     int key = e->key();

     if (!c->popup()->isVisible()) /* Traitement hors auto-completion */
     {
         switch(key)
         {
            /*----------------------------------*/
            case Qt::Key_Tab:

                if(e->modifiers() & Qt::ShiftModifier)
                    indentSelection(false);

                else if(!textCursor().hasSelection()) /* Tabulation classique */
                    QPlainTextEdit::keyPressEvent(e);

                else /* Tabulation d'une sélection */
                    indentSelection(true);

            return; /* On stop l'exécution de l'évènement ici */

            /*----------------------------------*/
            case Qt::Key_Enter:
            case Qt::Key_Return:
                if(!manageIndentation())
                    QPlainTextEdit::keyPressEvent(e);
             return; /* On stop l'exécution de l'évènement ici */

            /*---------------------------------*/
            case Qt::Key_Backtab:
                indentSelection(false);
            return;
            /*---------------------------------*/

            default: break;
         }
     }

    if (c->popup()->isVisible()) /* Traitement de l'auto-completion */
    {
        switch (key)
        {
            case Qt::Key_Enter:
            case Qt::Key_Return:
            case Qt::Key_Escape:
            case Qt::Key_Tab:
            case Qt::Key_Backtab:
                 e->ignore();
            return; /* Laisser l'autocomplétion gérer l'action par défaut */

            default: break;
        }
    }

     bool isShortcut = ((e->modifiers() & Qt::ControlModifier) && e->key() == Qt::Key_E);
     if (!c || !isShortcut)
         QPlainTextEdit::keyPressEvent(e);

     const bool ctrlOrShift = e->modifiers() & (Qt::ControlModifier | Qt::ShiftModifier);
     if (!c || (ctrlOrShift && e->text().isEmpty()))
         return;

     static QString eow("~!@#$%^&*()_+{}|:\"<>?,./;'[]\\-="); /* Fin du mot */
     bool hasModifier = (e->modifiers() != Qt::NoModifier) && !ctrlOrShift;
     QString completionPrefix = textUnderCursor();

     if (!isShortcut && (hasModifier || e->text().isEmpty()|| completionPrefix.length() < 3 || eow.contains(e->text().right(1)))) {
         c->popup()->hide();
         return;
     }

     if (completionPrefix != c->completionPrefix()) {
         c->setCompletionPrefix(completionPrefix);
         c->popup()->setCurrentIndex(c->completionModel()->index(0, 0));
     }
     QRect cr = cursorRect();
     cr.setWidth(c->popup()->sizeHintForColumn(0)
                 + c->popup()->verticalScrollBar()->sizeHint().width());
     c->complete(cr); /* Affiche la liste d'autocompletion */
 }

 void TextEdit::showDocumentation(QString cmd)
 {
    QFile in("script_commands.txt");

    if (!in.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        emit documentationReady(tr("Aucun résultat dans script_commands.txt.\n\nUtilisation de la documentation rapide (F1):\n- Placez le curseur sur une commande référencée dans la documentation.\n- Appuyez sur F1 pour afficher/cacher l'aide."), tr("Aucun résultat"));
        return;
    }

    QString text = in.readAll();
    in.close();

    QRegExp commandStart("[\\*]{1}"+cmd+"{1}[\\s({;]", Qt::CaseInsensitive);
    int start = commandStart.indexIn(text);

    if(start != -1)
    {
        QRegExp commandEnd("------------------------");
        int end = commandEnd.indexIn(text, start + commandStart.matchedLength());

        emit documentationReady(text.mid(start, end - start), cmd);
    }

    else
        emit documentationReady(tr("Aucun résultat dans script_commands.txt.\n\nUtilisation de la documentation rapide (F1):\n- Placez le curseur sur une commande référencée dans la documentation.\n- Appuyez sur F1 pour afficher/cacher l'aide."), tr("Aucun résultat"));
}


 int TextEdit::lineNumberAreaWidth()
 {
     int digits = 1;
     int max = qMax(1, blockCount());
     while (max >= 10) {
         max /= 10;
         ++digits;
     }

     int space = 3 + fontMetrics().width(QLatin1Char('9')) * digits;

     return space+5; /* +5 : Esthétique */
 }



 void TextEdit::updateLineNumberAreaWidth(int /* newBlockCount */)
 {
     if(lineNumberArea->isVisible())
        setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
 }



 void TextEdit::updateLineNumberArea(const QRect &rect, int dy)
 {
     if (dy)
         lineNumberArea->scroll(0, dy);
     else
         lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());

     if (rect.contains(viewport()->rect()))
         updateLineNumberAreaWidth(0);

     if(lineNumberArea->isVisible()) setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
 }



 void TextEdit::resizeEvent(QResizeEvent *e)
 {
     QPlainTextEdit::resizeEvent(e);

     QRect cr = contentsRect();
     lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), lineNumberAreaWidth(), cr.height()));
 }

void TextEdit::showLineNumberArea(bool show)
{
    if(!show)
    {
        lineNumberArea->hide();
        setViewportMargins(0, 0, 0, 0);
    }

    else
    {
        setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
        lineNumberArea->show();
    }
}

 void TextEdit::highlightCurrentLine()
 {
     QList<QTextEdit::ExtraSelection> extraSelections;

     if (!isReadOnly()) {
         QTextEdit::ExtraSelection selection;

         QColor lineColor = QColor(Qt::yellow).lighter(160);

         selection.format.setBackground(lineColor);
         selection.format.setProperty(QTextFormat::FullWidthSelection, true);
         selection.cursor = textCursor();
         selection.cursor.clearSelection();
         extraSelections.append(selection);
     }

     setExtraSelections(extraSelections);
 }



 void TextEdit::lineNumberAreaPaintEvent(QPaintEvent *event)
 {
     QPainter painter(lineNumberArea);
     painter.fillRect(event->rect(), Qt::lightGray);


     QTextBlock block = firstVisibleBlock();
     int blockNumber = block.blockNumber();
     int top = (int) blockBoundingGeometry(block).translated(contentOffset()).top();
     int bottom = top + (int) blockBoundingRect(block).height();

     while (block.isValid() && top <= event->rect().bottom()) {
         if (block.isVisible() && bottom >= event->rect().top()) {
             QString number = QString::number(blockNumber + 1)+" ";
             painter.setPen(Qt::black);
             painter.drawText(0, top, lineNumberArea->width(), fontMetrics().height(),
                              Qt::AlignRight, number);
         }

         block = block.next();
         top = bottom;
         bottom = top + (int) blockBoundingRect(block).height();
         ++blockNumber;
     }
}



bool TextEdit::manageIndentation()
{
    QTextCursor cursor = textCursor();
    QRegExp tabPattern("^(\\t*)");
    QString blockText = cursor.block().text();

    if (tabPattern.indexIn(blockText) > -1)
    {
        cursor.beginEditBlock();

        int nbTab = tabPattern.cap(1).length();
        QString tabs(nbTab, '\t');

        cursor.insertBlock();
        cursor.insertText(tabs);

        if(QRegExp("\\{").indexIn(blockText) > -1)
        {
            cursor.insertText("\t");
            if(QRegExp("\\}").indexIn(blockText) == -1)
            {
                cursor.insertBlock();
                cursor.insertText(tabs+"}");

                /* Place le curseur dans le nouveau scope {} */
                cursor.movePosition(QTextCursor::PreviousBlock, QTextCursor::MoveAnchor);
                cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::MoveAnchor);
            }
        }

        cursor.endEditBlock();
        setTextCursor(cursor);

        return true;
    }

    return false;

}

void TextEdit::indentSelection(bool indentation)
{
    QTextCursor cursor = textCursor();
    QTextBlock startBlock = document()->findBlock(cursor.selectionStart());
    QTextBlock nextBlock = startBlock; // sert d'élément glissant dans la liste des lignes sélectionnées
    QTextBlock endBlock = document()->findBlock(cursor.selectionEnd());

    bool multiline = false;

    if(cursor.hasSelection())
        if(cursor.selection().toPlainText().contains(QString("\n")))
            multiline = true;

    if(indentation)
    {
        if(multiline)
        {
            while(nextBlock != endBlock)
            {
                cursor.setPosition(nextBlock.position());
                cursor.movePosition(QTextCursor::StartOfBlock); // retour au début du block
                cursor.insertText("\t");
                // block suivant (= ligne sélectionnée suivante)
                nextBlock = nextBlock.next();
            }

            // une dernière fois car on ne l'a pas fait pour "endBlock"
            // étant donné que isValid() des TextBlock n'est pas fiable, c'est la solution la plus propre
            cursor.setPosition(nextBlock.position());
            cursor.movePosition(QTextCursor::StartOfBlock); // retour au début du block
            cursor.insertText("\t");
        }
    }

    else /* Unindent (SHIFT+TAB) */
    {
        if(multiline) /* Multiple unindent */
        {
            while(nextBlock != endBlock)
            {
                cursor.setPosition(nextBlock.position());
                cursor.movePosition(QTextCursor::StartOfBlock); // retour au début du block
                cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor); // sélection du premier caractère du block
                if(cursor.selectedText() == "\t") cursor.deleteChar(); // si c'est un tabulation, on l'efface

                // block suivant (= ligne sélectionnée suivante)
                nextBlock = nextBlock.next();
            }

            // une dernière fois car on ne l'a pas fait pour "endBlock"
            // étant donné que isValid() des TextBlock n'est pas fiable, c'est la solution la plus propre
            cursor.setPosition(nextBlock.position());
            cursor.movePosition(QTextCursor::StartOfBlock);
            cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
            if(cursor.selectedText() == "\t") cursor.deleteChar();
        }

        else // même traitement qu'en multiline (mais une seule fois)
        {
            cursor.movePosition(QTextCursor::StartOfBlock);
            cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor);
            if(cursor.selectedText() == "\t") cursor.deleteChar();
        }
    }
}
